package cgs

import (
	"cgs-server/helper"
	"cgs-server/pkg/set"
	"cgs-server/server"
	"cgs-server/server/base"
	"cgs-server/server/model"
	"errors"
	"fmt"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/bson/primitive"
	"go.mongodb.org/mongo-driver/mongo/options"
	"io/ioutil"
	"os"
	"regexp"
	"strings"
	"time"
)

type CgsService interface {
	Add(model *CgsAddModel) *base.AddResult // 2020-01-14 目前被用到一次
	Export(id primitive.ObjectID) ([]*model.ZipFileData, set.Set, error)
	Import(data []*model.ZipFileData, sceneID string, visualId string, formIdData *bson.M, user *model.User) (string, error)

	base.Service

	// 修改名称
	ChangeName(mdl *model.DataUserModel) error
	// 根据应用 id 获取
	GetByPublishId(publishId string, user *model.User) (*CgsProj, error)

	// 根据 PublishId 查询 应用
	GetVisualByPublishId(projectId, visualType string) (*model.Visual, error)

	// ListJs 获取js列表
	ListJs(mdl *model.DataUserModel) (interface{}, error)
}

func NewCgsService() CgsService {
	return &cgsService{
		cgsRepo: NewCgsRepository(),
	}
}

type cgsService struct {
	cgsRepo CgsRepository
	base.ServiceImpl
}

func (c *cgsService) ListJs(mdl *model.DataUserModel) (interface{}, error) {
	cgsProj := mdl.Data.(*CgsProj)

	parentId, err := primitive.ObjectIDFromHex(cgsProj.ParentId)
	if err != nil {
		return nil, err
	}

	mdl.Filter = &bson.M{
		"FileType": cgsProj.FileType,
		"ParentId": parentId,
	}

	return c.cgsRepo.Find(mdl)
}

func (c *cgsService) Add(model *CgsAddModel) *base.AddResult {
	// name, public, data
	model.IDObjId = primitive.NewObjectID()
	model.ID = model.IDObjId

	if model.Name == "" || strings.HasPrefix(model.Name, "_") {
		return &base.AddResult{Error: errors.New("name is not allowed to be empty or start with _")}
	}

	now := time.Now()
	//save file to /Upload/Cgs/$ID/timestap.js
	fileName := fmt.Sprintf("%v.js", helper.TimeToString(now, "yyyyMMddHHmmss"))
	savePath := fmt.Sprintf("/Upload/Cgs/%v", model.ID.Hex())
	filePath := fmt.Sprintf("%v/%v", savePath, fileName)
	physicalPath := server.MapPath(savePath)

	if _, err := os.Stat(physicalPath); os.IsNotExist(err) {
		_ = os.MkdirAll(physicalPath, 0755)
	}
	target, err := os.Create(fmt.Sprintf("%v/%v", physicalPath, fileName))
	if err != nil {
		return &base.AddResult{Error: err}
	}
	defer target.Close()
	_, err = target.WriteString(model.Data)

	model.FilePath = filePath

	// 创建数据库连接
	err = c.cgsRepo.CreateDB(server.CgsProjectCollectionName)
	if err != nil {
		return &base.AddResult{Error: err}
	}

	// 数据库新增
	_, err = c.cgsRepo.Add(model)
	if err != nil {
		return &base.AddResult{Error: err}
	}

	return &base.AddResult{Id: model.ID.Hex(), Msg: "Add successfully.", Error: nil}
}

func (c *cgsService) Export(id primitive.ObjectID) ([]*model.ZipFileData, set.Set, error) {
	formIdSet := set.NewSet()
	result, _ := c.cgsRepo.FindByProjectId(id)
	res, _ := helper.ToJSON(result)
	physicalPath := server.MapPath(result.FilePath)

	jsFile, err := ioutil.ReadFile(physicalPath)
	if err != nil {
		fmt.Println("read fail", err)
	}

	LastIndex := strings.LastIndex(result.FilePath, "/")
	jsonFile, err := ioutil.ReadFile(server.MapPath(result.FilePath[:LastIndex] + "/config.json"))
	if err != nil {
		fmt.Println("read fail", err)
	}

	var zipData []*model.ZipFileData
	if len(jsFile) > 0 {
		zipData = append(zipData, &model.ZipFileData{
			Name: server.ExportCgsFile + "project.js",
			Body: jsFile,
		})
	}
	if len(jsonFile) > 0 {
		zipData = append(zipData, &model.ZipFileData{
			Name: server.ExportCgsFile + "config.json",
			Body: jsonFile,
		})
		formIdSet = result.GetFormIds(string(jsonFile))
	}
	if len(res) > 0 {
		zipData = append(zipData, &model.ZipFileData{
			Name: server.ExportCgsFile + "cgsProject.json",
			Body: res,
		})
	}

	return zipData, formIdSet, nil
}

func (c *cgsService) Import(data []*model.ZipFileData, sceneID string, visualId string, formIdData *bson.M, user *model.User) (string, error) {
	projectId := primitive.NewObjectID()
	savePath := fmt.Sprintf("/Upload/Cgs/%v", projectId.Hex())
	physicalPath := server.MapPath(savePath)

	for _, value := range data {
		if strings.HasSuffix(value.Name, "config.json") {
			jsonData := string(value.Body)
			for key, value := range *formIdData {
				jsonData = strings.ReplaceAll(jsonData, key, value.(string))
			}
			//configPath := fmt.Sprintf("%v/%v", savePath, "config.json")
			if _, err := os.Stat(physicalPath); os.IsNotExist(err) {
				os.MkdirAll(physicalPath, 0755)
			}
			target, err := os.Create(fmt.Sprintf("%v/%v", physicalPath, "config.json"))
			defer target.Close()
			if err != nil {
				return "", err
			} else {
				_, err = target.WriteString(jsonData)
			}
		} else if strings.HasSuffix(value.Name, "cgsProject.json") {
			jsData := ""

			//1、创建js文件路径
			now := time.Now()
			fileName := fmt.Sprintf("%v.js", helper.TimeToString(now, "yyyyMMddHHmmss"))
			filePath := fmt.Sprintf("%v/%v", savePath, fileName)

			for _, value := range data {
				if strings.HasSuffix(value.Name, "project.js") {
					//1、修改js中的sceneID
					jsData = string(value.Body)
					//通过project中的js文件获得sceneID
					r := regexp.MustCompile(`.*sceneID\s*:\s*['|"](\w*)['|"]`)
					sceneArr := r.FindStringSubmatch(jsData)

					if len(sceneArr) < 2 {
						return "", errors.New("not find SceneID")
						//zipData, errors.New("not find SceneID")
					}
					jsData = strings.ReplaceAll(jsData, sceneArr[1], sceneID)

					// 替换 AppID
					r = regexp.MustCompile(`.*appID\s*:\s*['|"](\w*)['|"]`)
					appArr := r.FindStringSubmatch(jsData)

					if len(appArr) >= 2 {
						jsData = strings.ReplaceAll(jsData, appArr[1], visualId)
					}

					//save file to /Upload/Cgs/$ID/timestap.js
					//fmt.Println(filePath)

					if _, err := os.Stat(physicalPath); os.IsNotExist(err) {
						os.MkdirAll(physicalPath, 0755)
					}
					target, err := os.Create(fmt.Sprintf("%v/%v", physicalPath, fileName))
					defer target.Close()
					if err != nil {
						return "", err
					} else {
						_, err = target.WriteString(jsData)
					}
					break
				}
			}
			//3、修改js路径

			var result *CgsProj
			err := helper.FromJSON(value.Body, &result)
			if err != nil {
				return "", err
			}
			result.FilePath = filePath
			result.ID = projectId
			//result.Name += "_copy"
			result.PublishId = visualId
			result.Data = jsData
			result.JsPath = filePath
			result.SceneId = sceneID

			result.UserId = user.UserId.Hex()
			result.TenantId = user.TenantId
			// 创建数据库连接
			err = c.cgsRepo.CreateDB(server.CgsProjectCollectionName)
			if err != nil {
				return "", err
			}

			// 数据库新增
			err = c.cgsRepo.Insert(&model.DataUserModel{
				Data: result,
				User: user,
			})
			if err != nil {
				return "", err
			}
			//todo 更改userID
		}
	}

	return projectId.Hex(), nil
}

func (c *cgsService) Save(mdl *model.DataUserModel) error {
	if cgsProj, ok := mdl.Data.(*CgsProj); ok {
		// 1. 判断id是否存在，存在就修改，否则就新增
		isAdd := false
		if cgsProj.ID.IsZero() {
			isAdd = true
			cgsProj.ID = primitive.NewObjectID()
		} else {
			isAdd = false
		}
		// 2. 更新js文件
		now := time.Now()
		//save file to /Upload/Cgs/$ID/timestap.js
		fileName := fmt.Sprintf("%v.js", helper.TimeToString(now, "yyyyMMddHHmmss"))
		savePath := fmt.Sprintf("/Upload/Cgs/%v", cgsProj.ID.Hex())
		filePath := fmt.Sprintf("%v/%v", savePath, fileName)
		physicalPath := server.MapPath(savePath)
		if _, err := os.Stat(physicalPath); os.IsNotExist(err) {
			os.MkdirAll(physicalPath, 0755)
		}
		target, err := os.Create(fmt.Sprintf("%v/%v", physicalPath, fileName))
		if err != nil {
			return err
		}
		defer target.Close()
		_, err = target.WriteString(cgsProj.Data)

		cgsProj.FilePath = filePath
		cgsProj.JsPath = filePath

		sceneId, _ := cgsProj.GetSceneId()
		cgsProj.SceneId = sceneId
		if isAdd {
			err = c.cgsRepo.Insert(mdl)
		} else {
			err = c.cgsRepo.Update(mdl)
		}
		return err
	} else {
		return errors.New("CgsProj type error")
	}
}

func (c *cgsService) Update(mdl *model.DataUserModel) error {
	if cgsProj, ok := mdl.Data.(*CgsProj); ok {
		if cgsProj.ID.IsZero() {
			return errors.New("CgsProj ID is empty")
		}
		now := time.Now()
		//save file to /Upload/Cgs/$ID/timestap.js
		fileName := fmt.Sprintf("%v.js", helper.TimeToString(now, "yyyyMMddHHmmss"))
		savePath := fmt.Sprintf("/Upload/Cgs/%v", cgsProj.ID.Hex())
		filePath := fmt.Sprintf("%v/%v", savePath, fileName)
		physicalPath := server.MapPath(savePath)

		if _, err := os.Stat(physicalPath); os.IsNotExist(err) {
			os.MkdirAll(physicalPath, 0755)
		}
		target, err := os.Create(fmt.Sprintf("%v/%v", physicalPath, fileName))
		if err != nil {
			return err
		} else {
			_, err = target.WriteString(cgsProj.Data)
		}
		defer target.Close()
		cgsProj.FilePath = filePath
		cgsProj.JsPath = filePath
		sceneId, _ := cgsProj.GetSceneId()
		cgsProj.SceneId = sceneId
		err = c.cgsRepo.Update(mdl)
		return err
	} else {
		return errors.New("CgsProj type error")
	}
}

func (c *cgsService) ChangeName(mdl *model.DataUserModel) error {
	if cgsProj, ok := mdl.Data.(*CgsProj); ok {
		if cgsProj.ID.IsZero() {
			return errors.New("CgsProj ID is empty")
		}
		err := c.cgsRepo.Update(mdl)
		return err
	} else {
		return errors.New("CgsProj type error")
	}
}

func (c *cgsService) Remove(mdl *model.DataUserModel) error {
	if cgsProj, ok := mdl.Data.(*CgsProj); ok {
		if cgsProj.ID.IsZero() {
			return errors.New("CgsProj ID is empty")
		}
		// 查询cgsproject
		one, err := c.cgsRepo.FindOne(mdl)
		if err != nil {
			return err
		}
		if one == nil {
			return model.NotFoundError("cgs project")
		}
		cgsResult := one.(*CgsProj)
		err = c.cgsRepo.Delete(mdl)
		cgsProj.SceneId, _ = cgsResult.GetSceneId()
		cgsProj.Data = cgsResult.Data
		return err
	} else {
		return errors.New("CgsProj type error")
	}
}

func (c *cgsService) List(mdl *model.DataUserModel) (interface{}, error) {
	if cgsProj, ok := mdl.Data.(*CgsProj); ok {
		opts := options.FindOptions{
			Sort: bson.M{
				"updateTime": -1,
				"UpdateTime": -1,
			},
		}
		filter := bson.M{}
		if mdl.User == nil || mdl.User.UserId.IsZero() {
			// 匿名访客
			filter["IsPublic"] = true
		} else {
			// 已登录用户
			if mdl.User.RoleName == "AppAdministrator" || mdl.User.RoleName == "Administrator" {
				// 可以查看当前租户下的所有project

			} else {
				if len(mdl.User.AppAuths) == 0 {
					// 没有任何应用，表示没有任何 Project
					return nil, nil
				}
				ids := bson.A{}
				for k := range mdl.User.AppAuths {
					ids = append(ids, k)
				}
				filter = bson.M{
					"$or": bson.A{
						bson.M{
							"PublishId": bson.M{
								"$in": ids,
							},
						},
						bson.M{
							"IsPublic": true,
						},
					},
				}
			}
		}
		if cgsProj.Type != "" {
			filter["type"] = cgsProj.Type
			/*filter2 := bson.A{
				bson.M{
					"$or": bson.A{
						bson.M{
							"type": cgsProj.Type,
						},
						bson.M{
							"FileType": bson.M{"$ne": ""},
						},
					},
				},
			}

			if filter["$or"] != nil {
				filter2 = append(filter2, bson.M{"$or": filter["$or"]})
			}
			filter = bson.M{"$and": filter2}*/
		} else {
			filterArr := bson.A{
				bson.M{
					"type": bson.M{
						"$ne": "Earth",
					},
				},
				bson.M{
					"type": bson.M{
						"$ne": "TwoD",
					},
				},
				bson.M{
					"type": bson.M{
						"$ne": "Bim",
					},
				},
			}
			/*filter["type"] = bson.M{
				"$ne": "Earth",
			}*/

			if filter["$and"] != nil {
				if and, ok := filter["$and"].(primitive.A); ok {
					and = append(and, filterArr...)
					filter["$and"] = and
				}
			} else {
				filter["$and"] = filterArr
			}
		}
		mdl.Filter = &filter
		mdl.Options = &opts
		// 查询数据库
		results, err := c.cgsRepo.Find(mdl)
		if err != nil {
			return nil, err
		}
		return results, nil
	} else {
		return nil, errors.New("CgsProj type error")
	}
}

func (c *cgsService) GetOne(mdl *model.DataUserModel) (interface{}, error) {
	if cgsProj, ok := mdl.Data.(*CgsProj); ok {
		if cgsProj.ID.IsZero() {
			return nil, errors.New("CgsProj ID is empty")
		}
		result, err := c.cgsRepo.FindOne(mdl)
		return result, err
	} else {
		return nil, errors.New("CgsProj type error")
	}
}

func (c *cgsService) GetByPublishId(publishId string, user *model.User) (*CgsProj, error) {
	filter := bson.M{
		"PublishId": publishId,
	}
	duModel := &model.DataUserModel{
		Data:   &CgsProj{},
		User:   user,
		Filter: &filter,
	}
	result, err := c.cgsRepo.FindOne(duModel)
	if err != nil {
		return nil, err
	}
	if result != nil {
		return result.(*CgsProj), nil
	}
	return nil, errors.New("Cgs project is not found.")
}

func (c *cgsService) GetVisualByPublishId(projectId, visualType string) (*model.Visual, error) {
	return c.cgsRepo.GetVisualByPublishId(projectId, server.CgsPortalVisualCollectionName+visualType)
}
